package com.motu.vertx.module.utility.base;

import com.motu.vertx.module.utility.commondata.ServerOpenTask;
import com.motu.vertx.module.utility.commondata.ServeropenConfig;
import com.motu.vertx.module.utility.mysql.MysqlClient;
import com.motu.vertx.module.utility.mysql.MysqlTool;
import com.motu.vertx.module.utility.toolset.TimeUtils;
import com.motu.vertx.module.utility.toolset.Tool;
import io.vertx.core.*;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ServerOpenBase {
    public static final int LOG_OPEN_TYPE_0 = 0;// 日志的开服类型, 自动开服
    public static final int LOG_OPEN_TYPE_1 = 1;// 日志的开服类型, 立即开服
    public static final int LOG_OPEN_TYPE_2 = 2;// 日志的开服类型, 预约开服

    public static final int AUTOOPEN_TYPE_0 = 0;//  自动开服的方式 条件开服
    public static final int AUTOOPEN_TYPE_1 = 1;//  自动开服的方式 时间开服,固定整点开服

    public static final int CHECK_TYPE_0 = 0;//条件开服 注册人数(设备排重)
    public static final int CHECK_TYPE_1 = 1;//条件开服 角色等级达到N的人数(设备排重)
    public static final int CHECK_TYPE_2 = 2;//条件开服 付费用户的人数(设备排重)


    private static Logger logger = LoggerFactory.getLogger(ServerOpenBase.class);
    private static Vertx vertxInstance;
    public static String gmAdress = "";// eventbus的地址
    private static WebClient webClient;
    private static ServeropenConfig serverConfig = null;//自动开服配置表
    private static JsonObject serverConfigJsonObject;//
    public static ServerOpenInterFace serverOpenInterFace;

    // 自动开服的定时器列表
    public static JsonArray  timerList = new JsonArray();

    // 预约开服
    public static ExecutorService execServerOpenTask = Executors.newSingleThreadExecutor();// 预约开服任务队列执行者
    public static DelayQueue<ServerOpenTask> serverOpenQueue = new DelayQueue<>(); // 预约开服任务队列

    // 初始化
    public static void init(Vertx vertx) {
        vertxInstance = vertx;
        webClient = WebClient.create(vertxInstance, new WebClientOptions().setKeepAlive(true));
        initExecutorService();
        List<Future> futures = new ArrayList<>();
        futures.add(initServeropen_config());// 初始化定时任务执行器
        futures.add(initServerOpenTask());
        CompositeFuture.all(futures).onComplete(handler -> {
            if (handler.succeeded()) {
                startAutoOpenServer();// 开始自动开服
            } else {
                logger.error("###init error!!!");
            }
        });
    }

    // 重载
    public static void reload() {
        clearTimer();// 清除所有的定时器
        List<Future> futures = new ArrayList<>();
        futures.add(initServeropen_config());// 初始化定时任务执行器
        futures.add(initServerOpenTask());
        CompositeFuture.all(futures).onComplete(handler -> {
            if (handler.succeeded()) {
                startAutoOpenServer();// 开始自动开服
            } else {
                logger.error("###init error!!!");
            }
        });
    }

    // 清除所有的定时器
    public static void clearTimer() {
        for (int i=0;i<timerList.size(); i++) {
            long timerId = timerList.getLong(i);
            vertxInstance.cancelTimer(timerId);
        }
        timerList = new JsonArray();
    }

    /**
     * 初始化自动开服配置表
     */
    public static Future<Void> initServeropen_config() {
        Promise<Void> fut = Promise.promise();
        String table = "serveropen_config";
        String sql = "select * from " + table + " order by id asc limit 1;";
        MysqlTool.queryJsonArrayFromDb(MysqlClient.getServerPool(), sql, new JsonArray(), res -> {
            if (res.succeeded() && res.result().size() == 1) {
                try {
                    JsonArray result = res.result();
                    logger.info("init " + table + " size:" + result.size());
                    serverConfig = new ServeropenConfig(result.getJsonObject(0));// 只有第一条有效
                    serverConfigJsonObject = result.getJsonObject(0);
                    fut.complete();
                } catch (Exception e) {
                    logger.error("init " + table + " error exception:" + Tool.getException(e));
                    fut.fail(res.cause());
                }
            } else {
                logger.error("init " + table + " error!!!");
                fut.fail(res.cause());
            }
        });
        return fut.future();
    }

    // 开始自动开服
    public static void startAutoOpenServer() {
        if (serverConfig == null) {
            logger.info("###Auto open server no stat serverConfig is null!!!");//
            return;// 有设定预约开服,则不开启自动开服,
        }
        if (serverConfig.getIsClose() == 1) {
            logger.info("###Auto open server no stat serverConfig is close!!!");//
            return;
        }
        logger.info("###Auto open server stat serverConfig: " + serverConfigJsonObject.toString());// 开始自动开服
        if (serverConfig.isOpenTypeUsernum()) {// 条件开服)
            handleOpenServerByNum();// 定量开服
        } else if (serverConfig.isOpenTypeTime()) {// 定点开服
            long diff = getTimeDiff(TimeUtils.getCurTime());
            logger.info("###Auto open server start diff: " + diff + " !!!!!!!!!!!!!");
            long timerId = vertxInstance.setTimer(diff * 1000L, handler1->{
                handleOpenServerByTime();// 定点开服 整点一到先执行一次
                long timerId2 = vertxInstance.setPeriodic(TimeUtils.HOUR * 1000l, handler2 -> {// 定点开服固定1小时执行一次
                    handleOpenServerByTime();// 定点开服
                });
                timerList.add(timerId2);
            });
            timerList.add(timerId);
        }
    }

    // 定量开服
    public static void handleOpenServerByNum() {
        long delay = serverConfig.getPeriodicTime();
        long timerId = vertxInstance.setPeriodic(delay*1000L, handler -> {
            if (checkServerOpenTime()) {
                int curServerId = Future.await(getLastServerId());
                int userNum = Future.await(countUserNum(curServerId));// 根据定量开服的条件，统计玩家数量
                if (userNum >= serverConfig.getParam()) {// 条件达到执行开服
                    serverOpenInterFace.doOpenServer(serverConfig.getServerNum(), LOG_OPEN_TYPE_0, "");
                }
            }
        });
        timerList.add(timerId);
    }

    // 定点开服，固定整点
    public static void handleOpenServerByTime() {
        logger.info("##Auto open server start very hour: " + TimeUtils.getTimeFullStr(TimeUtils.getCurTime()));
        if (checkServerOpenTime()) {
            serverOpenInterFace.doOpenServer(serverConfig.getServerNum(), LOG_OPEN_TYPE_0, "");
        }
    }

    /**
     * 自动开服完成后需要处理的逻辑，更新下次开服的最小时间，发放飞书消息,记录操作日志
     * @param openType 开服类型。自动开服手动开服，预约开服
     * @param curSeverId 当前的服务器id,开服前的最新服务器id
     * @param serverNum 开服数量
     * @param operator 操作者
     */
    public static void handleOpenServerFinish(int curSeverId, int serverNum, int openType, String operator) {
        logger.info("###openServerFinish !!!");
        updateNextTime();

        JsonArray serverIds = getOpenServerIdList(curSeverId, serverNum);// 获取新开服的服务器Id 列表
        String openSS = "自动开服成功!";
        if (openType == LOG_OPEN_TYPE_1) {
            openSS = "立即开服成功!";
        } else if (openType == LOG_OPEN_TYPE_2) {
            openSS = "预约开服成功!";
        }
        // 开服成功发送通知到飞书
        if (!Tool.isEmpty(serverConfig.getFeishubot())) {
            int userNum = Future.await(getUserNumByServerId(curSeverId));// 上一服的注册人数
            String url = serverConfig.getFeishubot();// 发送的飞书机器人地址
            String content = serverConfig.getGameName() + "\n上一服: S" + curSeverId + " 注册人数:" + userNum + "人\n新服: S" + serverIds.toString() + " " + openSS + "\n时间: " + TimeUtils.getTimeFullStr(TimeUtils.getCurTime());// 预警内容
            Tool.sendToFeishu(webClient, content, url);
        }

        // 添加日志
        addLog(openType, openSS, operator, serverIds);
    }

    // 下次开服的最小时间,有设置间隔天数,开服成功时写入
    public static void updateNextTime() {
        if (serverConfig.getIntervalDay() > 0) {
            long nextTime = TimeUtils.getCurTime() + serverConfig.getIntervalDay() * Tool.DAY - Tool.MINUTE;// 预留1分钟,数据库入库等操作的耗时
            String sql = "update serveropen_config set nexttime = ? where id = ?;";
            JsonArray params = new JsonArray().add(nextTime).add(serverConfig.getId());
            logger.info("###AutoOpenServer update nexttime:" + Tool.getSqlString(sql, params));
            MysqlTool.updateToDb(MysqlClient.getServerPool(), sql, params, h1->{
                if (h1.succeeded()) {
                    initServeropen_config();// 被nexttime加载到内存
                }
            });
        }
    }


    /**
     * 记录日志
     * @param openType 日志类型
     * @param content 操作内容
     * @param operator 操作者
     * @param serverIds 服务器Id
     */
    public static void addLog(int openType, String content, String operator, JsonArray serverIds) {
        String sql = "insert into serveropen_log (opentype,content,operator,serverids,addtime) values(?,?,?,?,?);";
        JsonArray params = new JsonArray();
        params.add(openType);
        params.add(content);
        params.add(operator);
        params.add(serverIds.toString());
        params.add(TimeUtils.getCurTime());
        MysqlTool.updateToDb(MysqlClient.getServerPool(), sql, params, h1->{});
    }

    /**
     * 初始化执行器
     */
    public static void initExecutorService() {
        // 执行定时任务线程
        execServerOpenTask.execute(() -> {
            try {
                while (!Thread.interrupted()) {
                    serverOpenQueue.take().run();
                }
            } catch (Exception e) {
                logger.error("serverOpenTask take error: " + Tool.getException(e));
            }
        });
    }

    /**
     * 初始化预约开服
     */
    public static Future<Void> initServerOpenTask() {
        Promise<Void> fut = Promise.promise();
        // 清空定时任务
        serverOpenQueue.clear();
        // 添加定时任务
        String sql = "SELECT * FROM serveropen_order where opentime > ? order by opentime;";
        JsonArray params = new JsonArray().add(TimeUtils.getCurTime());
        MysqlTool.queryJsonArrayFromDb(MysqlClient.getServerPool(), sql, params, handler -> {
            if (handler.succeeded()) {
                JsonArray array = handler.result();
                if (array != null && array.size() > 0) {
                    for (int i = 0, size = array.size(); i < size; i++) {
                        JsonObject obj = array.getJsonObject(i);
                        ServerOpenTask task = new ServerOpenTask(obj.getInteger("servernum"), obj.getInteger("opentime"));
                        serverOpenQueue.put(task);
                    }
                    logger.info("##init serveropen task size:" + array.size());
                }
                fut.complete();
            } else {
                logger.error(handler.cause().getMessage());
                fut.fail(handler.cause());
            }
        });
        return fut.future();
    }

    // 判断当前时间是否在 可开服的时间内，>= 最早开服时间，星期几，定量开服必须在开服时间段内，定点开服无需在配置的开服时间段内 返回false 不能开服
    public static boolean checkServerOpenTime() {
        long curTime = TimeUtils.getCurTime();//  当前的时间戳

        if (serverConfig.getNextTime() != 0 && curTime < serverConfig.getNextTime()) {
            logger.info("AutoOpenServer faild error nexttime:" + serverConfig.getNextTime());
            return false;// 有设置最早开服时间,且当前时间未到,则不开服
        }

        long tt = (long) curTime * 1000;
        Calendar cal = Calendar.getInstance();
        cal.setTimeInMillis(tt);
        int hour = cal.get(Calendar.HOUR_OF_DAY);//当前是第几点

        if (!Tool.isEmpty(serverConfig.getWeek())) {
            JsonArray weekList = new JsonArray(serverConfig.getWeek());
            if (weekList.size() > 0) {
                int curWeek = cal.get(Calendar.DAY_OF_WEEK) - 1;// 1,2,3,4,5,6,7 对应周1,周2,周3...周日
                if (curWeek == 0) {// 周日转成7
                    curWeek = 7;
                }
                if (!Tool.isInList(weekList, curWeek)) {
                    logger.info("AutoOpenServer faild error curWeek:" + curWeek + " weekList:" + weekList);
                    return false;
                }
            }
        }

        if (serverConfig.isOpenTypeTime()) {// 定点开服
            JsonArray jsonArray = new JsonArray(serverConfig.getHour());
            if (Tool.isInList(jsonArray, hour)) {
                return true;
            } else {
                return false;
            }
        } else {// 定量开服
            if (Tool.isEmpty(serverConfig.getStartTime()) || Tool.isEmpty(serverConfig.getEndTime())) {
                return true;// 未配置开服时间段
            }

            String day = TimeUtils.getDateStr(curTime * 1000);// 今天的日期2023-05-22
            long sTime = TimeUtils.getTimeStrIntValue(day + " " + serverConfig.getStartTime());// 今日00:15:00 对应的时间戳
            long eTime = TimeUtils.getTimeStrIntValue(day + " " + serverConfig.getEndTime());// 今日21:59:59 对应的时间戳
            if (curTime >= sTime && curTime <= eTime) {
                return true;
            } else {
                return false;
            }
        }
    }

    /**
     * 获取当前时间到整点的时间差. 比如当前时间12:30:00, 则返回 12:30:00 到 13:00:00 的时间差
     * @return
     */
    public static long getTimeDiff(long time) {
        long time0 = TimeUtils.getStartTimeOfDay(time);// 今天的0点0分0秒
        long time1 = 0L;// 下一个整点的时间戳
        for (int i=1; i<25; i++) {
            if (time0 + Tool.HOUR * i > time && time1 == 0) {
                time1 = time0 + Tool.HOUR * i;
            }
        }
        return time1 - time;
    }

    // 根据服务器id 获取该服的注册人数
    public static Future<Integer> getUserNumByServerId(int serverId) {
        Promise<Integer> fut = Promise.promise();
        String sql2 = "select count(distinct(device_id)) as num from user_ext_info where server_id=" + serverId;
        MysqlTool.queryJsonArrayFromDb(MysqlClient.getGamePool(), sql2, new JsonArray(), handler2->{
            if (handler2.succeeded()) {
                int num = handler2.result().getJsonObject(0).getInteger("num");
                fut.complete(num);
            } else {
                fut.complete(0);
            }
        });
        return fut.future();
    }

    // 根据定量开服的条件，统计玩家数量
    public static Future<Integer> countUserNum(int serverId) {
        Promise<Integer> fut = Promise.promise();
        String sql = "";//
        JsonArray jsonArray = new JsonArray();
        if (serverConfig.getCheckType() == CHECK_TYPE_0) {// 条件开服 注册人数(设备排重)
            sql = "select count(distinct(device_id)) as num from user_ext_info where server_id=?;";//开服条件 默认0-设备排重
            jsonArray.add(serverId);
        } else if (serverConfig.getCheckType() == CHECK_TYPE_1) {// 条件开服 角色等级达到N的人数(设备排重)
            sql = "select count(*) as num from user_info where server_id = ? and level >= ?;";// 角色等级>=6
            jsonArray.add(serverId);
            jsonArray.add(serverConfig.getParam2());// 角色等级配置在param2
        } else if (serverConfig.getCheckType() == CHECK_TYPE_1) {// 条件开服 付费用户的人数(设备排重)
            sql = "select count(distinct(device_id)) as num from user_ext_info where iap_buy > 0 and server_id=?;";
            jsonArray.add(serverId);
        }

        MysqlTool.queryJsonArrayFromDb(MysqlClient.getGamePool(), sql, jsonArray, handler2->{
            if (handler2.succeeded()) {
                int num = handler2.result().getJsonObject(0).getInteger("num");
                fut.complete(num);
            } else {
                fut.complete(0);
            }
        });
        return fut.future();
    }

    // 取最新的服务器id
    public static Future<Integer> getLastServerId() {
        Promise<Integer> fut = Promise.promise();
        String sql = "select server_id from server_info order by server_id desc limit 1;";
        MysqlTool.queryJsonArrayFromDb(MysqlClient.getServerPool(), sql, new JsonArray(), handler2->{
            if (handler2.succeeded()) {
                int num = handler2.result().getJsonObject(0).getInteger("server_id");
                fut.complete(num);
            } else {
                fut.complete(0);
            }
        });
        return fut.future();
    }

    /**
     * 获取新开服的服务器Id 列表
     * @param curServerId 开服前的最大服务器Id
     * @param num 开服数量
     * @return
     */
    public static JsonArray getOpenServerIdList(int curServerId, int num) {
        JsonArray serverIdList = new JsonArray();
        for (int i=0; i<num; i++) {
            serverIdList.add(curServerId + 1 + i);
        }
        return serverIdList;
    }

    public static ServeropenConfig getServerConfig() {
        return serverConfig;
    }

    public static Vertx getVertxInstance() {
        return vertxInstance;
    }
}
